iT邦幫忙

2026 iThome 鐵人賽

DAY 25
0
AI 自動化

AaaS from Scratch: 從一次性定義,到規模化分析系列 第 25 篇

[Day 25] Analysis Optimization Validation

  • 分享至 

  • xImage
  •  

上一篇做到這裡:

Run
↓
Check
↓
Finding
↓
User clicks Optimize
↓
Rewrite
↓
Candidate Recipe

但 Agent rewrite 完,不代表可以直接把 candidate 存成新的 Analysis version。

因為 optimization 不能只回答:

跑得更快了嗎?

還要回答:

rewrite 之後,有沒有改掉原本 Analysis 的結果?

所以 candidate 要先經過 Validation:

Candidate Recipe
↓
0. Recipe / Output checks
↓
1. Fits target datasource?
↓
2. Candidate runs on original datasource
↓
3. Compare v1 vs candidate
↓
Pass
→ Save as v2
→ Run v2 on target datasource

Validation 是 code,不是 model。

Agent 只負責提出 candidate;能不能成為新 version,由 save_analysis 裡的固定 checks 決定。:chatgpt-content-reference{index="0"}

0. Recipe 先要是合法的

Candidate 先要通過一般 Save Analysis 本來就有的 checks。

包含:

  • title / inputs / outputs 存在

  • input files

    • .parquet
    • filename 不重複
  • outputs

    • .html
  • script

    • 使用 DATA_DIR
    • 使用 OUTPUT_DIR
    • 有讀到每個 input file
  • SQL

    • 能 parse 成 PostgreSQL
  • script

    • 不能把 query result 直接 hard-code 進去

另外 optimization 還多一個要求:

candidate output filenames
=
current version output filenames

因為 optimization 可以改 execution strategy,但不能偷偷把 Analysis 的 outputs 改掉。

1. 新 Query 有解決大資料量的問題嗎?

接著先檢查 rewrite 後的 strategy,真的能不能處理原本出問題的大 datasource。

流程是:

Candidate SQL
↓
Swap to target datasource
↓
EXPLAIN
↓
Estimated Rows / Bytes

如果 estimate 沒超 hard limit,就繼續。

如果 estimate 超過 hard limit,不會馬上 reject。

因為 PostgreSQL planner 的 estimate 也可能不準。

這時會再做:

SELECT count(*)
FROM (<candidate query>) AS counted;

只把 row count 算出來,不把完整 result 搬進 sandbox。

目前 hard limits 是:

  • Rows: 100,000
  • Estimated Bytes: 200 MB

如果 optimization 原本處理的是 data_movement,要求會更嚴格:

  • Rows <= 50,000

也就是 rewrite 不能只是從 4M rows 變成 90K rows,然後說「已經低於 hard limit」。

既然問題是 data movement,就要真的把 data reduction 做下來。

為什麼不能只相信 EXPLAIN?

這裡碰過一個情況,原本的 Analysis 是計算:

過去 14 天,不同 hour × event type 的 event count heatmap。

Agent rewrite 成:

SELECT
    EXTRACT(HOUR FROM event_time)::int AS hour,
    event_type,
    COUNT(*) AS count
FROM events
WHERE event_time >= NOW() - INTERVAL '14 days'
GROUP BY 1, 2
ORDER BY 1, 2;

這個 query 實際上是在做:

4.2M raw events
↓
取出 event_time 的 hour
↓
按照 hour + event_type 分組
↓
每組 COUNT(*)

如果:

hour
→ 24 種:0 ~ 23

event_type
→ 5 種

那最後大約只會有:

24 × 5
= 120 groups
= 120 output rows

這是很直覺的結果,但 EXPLAIN 不是真的執行 query,它是在執行前,根據 PostgreSQL 已經收集好的 statistics,去估算每一步可能會產生多少 rows。

例如對一般 column,PostgreSQL 可能知道:

device_type
→ 大約 3 個 distinct values

event_type
→ 大約 5 個 distinct values

所以如果是:

GROUP BY device_type

會比較容易估出最後剩幾個 groups,但這次不是直接 group 一個 column,而是:

EXTRACT(HOUR FROM event_time)

也就是先把 timestamp 做 transformation:

2026-10-08 14:32:01
↓
EXTRACT(HOUR ...)
↓
14

對人來說,我們知道:

hour 一定只會是 0 ~ 23

但 planner 不一定有:

EXTRACT(HOUR FROM event_time)

這個表示的統計數字。

它可能知道 event_time 本身有很多不同的 timestamp,但不知道經過 EXTRACT(HOUR ...) 之後,其實只剩 24 個 distinct values。

所以在估:

GROUP BY EXTRACT(HOUR FROM event_time), event_type

時,就可能嚴重高估 group 數量。

最後變成:

Input rows
→ ~4.2M

EXPLAIN estimate
→ ~4.2M rows

Actual output
→ 120 rows

所以 EXPLAIN 的意思不是:

這個 query 一定會回傳 4.2M rows。

而是:

根據目前的 statistics,我估計可能會回傳這麼多 rows。

如果 validator 只相信 EXPLAIN:

4.2M
>
100K hard limit

就會直接 reject,但 rewrite 其實是正確的,真正執行後只會回傳 120 rows,因此現在的做法是:

EXPLAIN
↓
estimate 沒超過 limit
→ 繼續

estimate 超過 limit
↓
COUNT(*)
↓
確認實際 output row count

例如:

SELECT COUNT(*)
FROM (
    SELECT
        EXTRACT(HOUR FROM event_time)::int AS hour,
        event_type,
        COUNT(*) AS count
    FROM events
    WHERE event_time >= NOW() - INTERVAL '14 days'
    GROUP BY 1, 2
) AS counted;

這次 PostgreSQL 會真的執行裡面的 grouping,所以可以知道實際 result 是 120 rows 而不是 4.2M。

EXPLAIN estimate
→ ~4.2M

COUNT actual
→ 120

120 < 50K target
→ candidate fits

所以這裡的策略不是完全相信 EXPLAIN。

EXPLAIN 適合做便宜的 pre-check;只有 estimate 超過 limit 時,才用 COUNT(*) 再確認一次,避免 planner estimate 不準,把其實正確的 rewrite 誤判成失敗。

2. Candidate 先跑原本的 Datasource

Candidate 確認能 fit target datasource 後,還不能存。

接下來會回到 Analysis 原本建立時使用的 datasource。

這不是另外準備一份 test data,而是原本的 saved table。

例如:

Original datasource
→ events

Target datasource
→ events_enterprise

Candidate 會先用原本的 events:

Fetch inputs
↓
Check typed-in results
↓
Run script in empty folder
↓
Check declared outputs exist

Test run 使用乾淨的 scratch folder,避免 candidate 偷偷依賴之前 sandbox 留下來的檔案。

3. v1 和 Candidate 用同一份 Data 比結果

接著 current version 也會在同一份 datasource 上重新跑一次:

v1
→ original datasource
→ output A

candidate
→ same datasource
→ output B

compare A vs B

為什麼不是拿 target datasource 比?

因為 v1 很可能根本跑不了 target datasource。

所以原本的小 datasource 在這裡變成 reference:

same data
+
different execution strategy
↓
compare result

目前 comparison 只看 HTML 裡的 Plotly chart data:

x
y
z
values
labels

規則是:

Labels
→ sorted sets 必須相同

Numbers
→ 數量必須相同
→ 每個值允許 1% relative tolerance

Order
→ ignored

如果 output 沒有 Plotly chart data,會標記成 not compared,不會假裝已經驗證一致。

Validation Fail 不會改掉 v1

Validation 不是只有 pass / fail 一次。

如果其中一個 check 不通過:

Candidate
↓
Validation fails
↓
save_analysis returns exact reason
↓
Agent sees the reason
↓
Rewrite again
↓
save_analysis again

也就是 Rewrite 和 Validation 其實會形成一個 loop。

例如:

NOT SAVED:
candidate still exports too many rows

Agent 就可以再改 SQL。

如果最後還是沒有通過:

nothing saved
v1 unchanged

User 之後也可以再按 Optimize again,重新開一次 optimization conversation,或是直接問 Agent 問題出在什麼地方。

Pass 之後才有 Version 2

只有所有 checks 都通過,candidate 才會:

Candidate
↓
Validation passes
↓
Save as v2
↓
Keep v1 in version history
↓
Record OptimizationNote
↓
Run v2 on target datasource

所以:

Small datasource
→ validate same result

Large datasource
→ validate execution strategy fits
→ run optimized version

這兩件事是分開檢查的。

而且每個 version 對應固定的 recipe。

同一個 version 不會今天跑 SQL A、明天又偷偷換成 SQL B。

Validation 不是 Correctness Proof

目前這套 Validation 還是有清楚的 boundary。

它只能證明:

v1
and
candidate

在原本 saved datasource 上
產生相近的 chart result

它不能證明兩份 SQL 在所有可能的 data 上都 mathematically equivalent。

目前還有幾個限制:

只在 original datasource 比結果

large datasource 沒有 reference answer

只比較 Plotly chart values

numeric comparison 有 1% tolerance

所以 Validation 的目的不是證明 rewrite 永遠正確。

它是在 Agent 修改 execution strategy 之後,多加一層 deterministic guardrail,避免一個「跑得更快」的 optimization,把原本 Analysis 的結果一起改掉。


上一篇
[Day 24] Analysis Optimization:什麼時候該 Rewrite?
下一篇
[Day 26] Analysis的版本控制
系列文
AaaS from Scratch: 從一次性定義,到規模化分析 共 29 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言